package com.hzw.saas.web.app.project.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.extra.servlet.ServletUtil;
import com.hzw.saas.api.property.IPropertyService;
import com.hzw.saas.api.property.enmus.SysDictKeyEnum;
import com.hzw.saas.api.storage.IResourceFileService;
import com.hzw.saas.api.storage.bo.ResourceFileBO;
import com.hzw.saas.api.storage.bo.ResourceFileStorageBO;
import com.hzw.saas.api.storage.enums.StorageSpaceEnum;
import com.hzw.saas.common.config.annotation.SysLog;
import com.hzw.saas.common.config.util.AssertUtil;
import com.hzw.saas.common.flow.service.IFlowInstanceService;
import com.hzw.saas.common.security.utils.SecurityUtils;
import com.hzw.saas.common.util.Json;
import com.hzw.saas.common.util.MapperUtil;
import com.hzw.saas.common.util.enums.ConflictModeEnum;
import com.hzw.saas.service.project.bo.ProjectApplyBO;
import com.hzw.saas.service.project.bo.ProjectApplyNoticeBO;
import com.hzw.saas.service.project.bo.ProjectApplyScrollBo;
import com.hzw.saas.service.project.bo.ProjectAttachmentBO;
import com.hzw.saas.service.project.constants.ProjectConsts;
import com.hzw.saas.service.project.enums.AttachmentType;
import com.hzw.saas.service.project.model.ProjectApply;
import com.hzw.saas.service.project.model.ProjectAttachment;
import com.hzw.saas.service.project.service.IProjectApplyService;
import com.hzw.saas.service.project.service.IProjectAttachmentService;
import com.hzw.saas.service.storage.config.StorageConfig;
import com.hzw.saas.service.storage.util.StorageUtil;
import com.hzw.saas.web.app.project.param.ProjectAcceptanceUpdateParam;
import com.hzw.saas.web.app.project.param.ProjectApplyCreateParam;
import com.hzw.saas.web.app.project.param.ProjectApplyUpdateParam;
import io.swagger.annotations.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.Min;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 项目申请
 *
 * @author zzl
 * @since 08/11/2021
 */
@Controller
@RequiredArgsConstructor
@Slf4j
@Api(tags = "流程管理/项目申请")
public class ProjectApplyController {

    private final IProjectApplyService projectApplyService;
    private final IProjectAttachmentService projectAttachmentService;
    private final IFlowInstanceService flowInstanceService;

    private final StorageConfig storageConfig;
    private final IResourceFileService resourceFileService;

    @Resource(name = IPropertyService.SYS_DICT_BEAN)
    private final IPropertyService<SysDictKeyEnum> propertyService;

    @GetMapping("/apply/")
    @SysLog
    @ApiOperation(value = "获取项目申请详情", notes = "获取项目申请详情")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "flowInstanceId", value = "流程实例ID, 与项目申请ID必须有一项不为空"),
        @ApiImplicitParam(name = "projectApplyId", value = "项目申请ID"),
    })
    @ApiOperationSort(11)
    public ResponseEntity<ProjectApplyBO> getProjectApply(String flowInstanceId, String projectApplyId) {
        ProjectApplyBO projectApply = this.projectApplyService.getProjectApply(flowInstanceId, projectApplyId);
        return ResponseEntity.ok(projectApply);
    }

    @PostMapping(value = "/apply/project", consumes = {"multipart/form-data"})
    @Transactional(rollbackFor = Exception.class)
    @SysLog(operation = "提交新的项目申请")
    @ApiOperation(value = "提交新的项目申请", notes = "提交新的项目申请")
    @ApiOperationSort(12)
    public ResponseEntity<Void> submitProjectApply(@Validated ProjectApplyCreateParam param, HttpServletRequest request) {
        // 判断请求类型
        boolean multipart = ServletUtil.isMultipart(request);
        AssertUtil.assertThrow("上传失败，请求参数异常，请使用multipart/form-data类型上传", !multipart);
        this.createProjectApply(param, true);
        return ResponseEntity.ok().build();
    }

    @PostMapping(value = "/apply/project/draft", consumes = {"multipart/form-data"})
    @Transactional(rollbackFor = Exception.class)
    @SysLog(operation = "保存新的项目申请草稿")
    @ApiOperation(value = "保存新的项目申请草稿", notes = "保存新的项目申请草稿")
    @ApiOperationSort(13)
    public ResponseEntity<Void> stashProjectApply(ProjectApplyCreateParam param, HttpServletRequest request) {
        // 判断请求类型
        boolean multipart = ServletUtil.isMultipart(request);
        AssertUtil.assertThrow("上传失败，请求参数异常，请使用multipart/form-data类型上传", !multipart);
        this.createProjectApply(param, false);
        return ResponseEntity.ok().build();
    }

    @PostMapping(value = "/apply/project/update", consumes = {"multipart/form-data"})
    @Transactional(rollbackFor = Exception.class)
    @SysLog(operation = "提交项目申请草稿")
    @ApiOperation(value = "提交项目申请草稿", notes = "提交项目申请草稿")
    @ApiOperationSort(14)
    public ResponseEntity<Void> submitUpdatedProjectApply(@Validated ProjectApplyUpdateParam param, HttpServletRequest request) {
        // 判断请求类型
        boolean multipart = ServletUtil.isMultipart(request);
        AssertUtil.assertThrow("上传失败，请求参数异常，请使用multipart/form-data类型上传", !multipart);
        this.updateProjectApply(param, true);
        return ResponseEntity.ok().build();
    }

    @PostMapping(value = "/apply/project/draft/update", consumes = {"multipart/form-data"})
    @Transactional(rollbackFor = Exception.class)
    @SysLog(operation = "更新项目申请草稿")
    @ApiOperation(value = "更新项目申请草稿", notes = "保存项目申请草稿")
    @ApiOperationSort(15)
    public ResponseEntity<Void> stashUpdatedProjectApply(ProjectApplyUpdateParam param, HttpServletRequest request) {
        // 判断请求类型
        boolean multipart = ServletUtil.isMultipart(request);
        AssertUtil.assertThrow("上传失败，请求参数异常，请使用multipart/form-data类型上传", !multipart);
        this.updateProjectApply(param, false);
        return ResponseEntity.ok().build();
    }

    @PostMapping(value = "/apply/project/acceptance", consumes = {"multipart/form-data"})
    @Transactional(rollbackFor = Exception.class)
    @SysLog(operation = "提交项目验收审批")
    @ApiOperation(value = "提交项目验收审批", notes = "提交项目验收审批")
    @ApiOperationSort(16)
    public ResponseEntity<Void> submitProjectAcceptanceApply(@Validated ProjectAcceptanceUpdateParam param, HttpServletRequest request) {
        // 判断请求类型
        boolean multipart = ServletUtil.isMultipart(request);
        AssertUtil.assertThrow("上传失败，请求参数异常，请使用multipart/form-data类型上传", !multipart);
        this.updateProjectAcceptanceApply(param, true);
        return ResponseEntity.ok().build();
    }

    @PostMapping(value = "/apply/project/acceptance/draft", consumes = {"multipart/form-data"})
    @Transactional(rollbackFor = Exception.class)
    @SysLog(operation = "保存项目验收审批草稿")
    @ApiOperation(value = "保存项目验收审批草稿", notes = "保存项目验收审批草稿")
    @ApiOperationSort(17)
    public ResponseEntity<Void> stashProjectAcceptanceApply(@Validated ProjectAcceptanceUpdateParam param, HttpServletRequest request) {
        // 判断请求类型
        boolean multipart = ServletUtil.isMultipart(request);
        AssertUtil.assertThrow("上传失败，请求参数异常，请使用multipart/form-data类型上传", !multipart);
        this.updateProjectAcceptanceApply(param, false);
        return ResponseEntity.ok().build();
    }

    @GetMapping(value = "/apply/project/research/orientation")
    @Transactional(rollbackFor = Exception.class)
    @SysLog(operation = "获取项目研究方向列表")
    @ApiOperation(value = "项目研究方向列表", notes = "项目研究方向列表")
    @ApiOperationSort(21)
    public ResponseEntity<List<String>> getResearchOrientationList() {
        try {
            String propertyValue = propertyService.getPropertyValue(SysDictKeyEnum.RESEARCH_DIRECTION_JSON);
            List<String> researchDirections = Json.parseArray(propertyValue, String[].class);
            return ResponseEntity.ok(researchDirections);
        } catch (Exception e) {
            return ResponseEntity.ok(new ArrayList<>());
        }
    }

    @GetMapping(value = "/apply/project/{flowInstanceId}/export/pdf")
    @SysLog(operation = "项目申请导出PDF", mode = 3)
    @ApiOperation(value = "项目申请导出PDF", notes = "项目申请导出PDF")
    @ApiOperationSort(22)
    public void projectApplyExportPDF(@PathVariable @ApiParam("项目申请ID") String flowInstanceId, HttpServletResponse response) {
        projectApplyService.projectApplyExportPDF(flowInstanceId, response, false);
    }

    @GetMapping(value = "/apply/project/{pid}/attachments")
    @SysLog(operation = "获取附件列表")
    @ApiOperation(value = "获取附件列表", notes = "获取附件列表")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "pid", value = "项目申请ID", paramType = "path", dataType = "String", required = true),
        @ApiImplicitParam(name = "attachmentType", value = "附件类型，" + AttachmentType.API_TEXT, paramType = "query", dataType = "int", allowMultiple = true, required = true),
    })
    @ApiOperationSort(22)
    public ResponseEntity<List<ProjectAttachmentBO>> listAttachments(@PathVariable String pid, Integer[] attachmentType) {
        List<ProjectAttachmentBO> attachmentBOS = projectAttachmentService.listAttachmentBo(pid, attachmentType);
        return ResponseEntity.ok(attachmentBOS);
    }

    @GetMapping("/apply/project/notice/{count}")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "count", value = "需要的条数 eg:20", required = true, dataType = "int", paramType = "path"),
    })
    @ApiResponses({
            @ApiResponse(code = 200, message = "直接返回List eg:[aa,bb,cc]")
    })
    @ApiOperation(value = "项目审批通过公告", notes = "滚动信息-项目审批通过公告")
    public ResponseEntity<List<ProjectApplyNoticeBO>> getProjectNotices(@PathVariable @Min(1) Integer count) {
        List<ProjectApplyScrollBo> projectApplyScrollBos = projectApplyService.limitProjectScroll(count);
//        List<String> scrollInfos = projectApplyScrollBos.stream()
//                .map(ProjectApplyScrollBo::convert)
//                .collect(Collectors.toList());
        List<ProjectApplyNoticeBO> projectApplyNoticeBOS = projectApplyScrollBos.stream()
            .map(ProjectApplyScrollBo::toNotice)
            .collect(Collectors.toList());
        return ResponseEntity.ok(projectApplyNoticeBOS);
    }

    /**
     * 检查附件大小
     *
     * @param param
     * @return
     */
    private boolean checkAttachmentSize(ProjectApplyCreateParam param) {
        long totalSize = 0L;
        if (Objects.nonNull(param.getResearchPlanAttachments())) {
            for (MultipartFile researchPlanAttachment : param.getResearchPlanAttachments()) {
                totalSize += researchPlanAttachment.getSize();
            }
        }
        if (Objects.nonNull(param.getResearchPlanAttachments())) {
            for (MultipartFile resumeAttachment : param.getResearchPlanAttachments()) {
                totalSize += resumeAttachment.getSize();
            }
        }
        if (Objects.nonNull(param.getMagnumOpusAttachments())) {
            for (MultipartFile magnumOpusAttachment : param.getMagnumOpusAttachments()) {
                totalSize += magnumOpusAttachment.getSize();
            }
        }
        return totalSize <= ProjectConsts.MAX_ATTACHMENT_SIZE;
    }

    private boolean checkAttachmentSize(String projectApplyId, ProjectApplyUpdateParam param) {
        List<ProjectAttachment> list = projectAttachmentService.listProjectAttachment(projectApplyId,
            AttachmentType.RESEARCH_PLAN.code(), AttachmentType.RESUME.code(), AttachmentType.MAGNUM_OPUS.code());

        long totalSize = 0L;
        if (Objects.nonNull(param.getResearchPlanAttachments())) {
            for (MultipartFile researchPlanAttachment : param.getResearchPlanAttachments()) {
                totalSize += researchPlanAttachment.getSize();
            }
            long count = list.stream().filter(att -> Objects.equals(att.getAttachmentType(), AttachmentType.RESEARCH_PLAN.code())).count();
            if (count + param.getResearchPlanAttachments().length > ProjectConsts.MAX_ATTACHMENT_COUNT) {
                return false;
            }
        }
        if (Objects.nonNull(param.getResumeAttachments())) {
            for (MultipartFile resumeAttachment : param.getResumeAttachments()) {
                totalSize += resumeAttachment.getSize();
            }
            long count = list.stream().filter(att -> Objects.equals(att.getAttachmentType(), AttachmentType.RESUME.code())).count();
            if (count + param.getResumeAttachments().length > ProjectConsts.MAX_ATTACHMENT_COUNT) {
                return false;
            }
        }
        if (Objects.nonNull(param.getMagnumOpusAttachments())) {
            for (MultipartFile magnumOpusAttachment : param.getMagnumOpusAttachments()) {
                totalSize += magnumOpusAttachment.getSize();
            }
            long count = list.stream().filter(att -> Objects.equals(att.getAttachmentType(), AttachmentType.MAGNUM_OPUS.code())).count();
            if (count + param.getMagnumOpusAttachments().length > ProjectConsts.MAX_ATTACHMENT_COUNT) {
                return false;
            }
        }
        List<ResourceFileBO> resourceFileBOS = resourceFileService.listFiles(
            list.stream().map(ProjectAttachment::getResourceId).collect(Collectors.toList()));
        totalSize += resourceFileBOS.stream().mapToLong(ResourceFileBO::getLength).sum();
        return totalSize <= ProjectConsts.MAX_ATTACHMENT_SIZE;
    }

    private void createProjectApply(ProjectApplyCreateParam param, boolean isSubmit) {
        String userId = SecurityUtils.getUser().getUserId();
        // 校验附件大小
        AssertUtil.assertThrow("附件大小不可超过50M，单类附件不可超过5个", !this.checkAttachmentSize(param));
        // 创建项目申请
        ProjectApply projectApply = MapperUtil.nf().map(param, ProjectApply.class);
        projectApply.setUserId(userId);
        projectApply = projectApplyService.createProjectApply(projectApply, isSubmit);
        // 保存附件
        this.saveAttachmentFiles(userId, projectApply.getPid(), param.getResearchPlanAttachments(), AttachmentType.RESEARCH_PLAN);
        this.saveAttachmentFiles(userId, projectApply.getPid(), param.getResumeAttachments(), AttachmentType.RESUME);
        this.saveAttachmentFiles(userId, projectApply.getPid(), param.getMagnumOpusAttachments(), AttachmentType.MAGNUM_OPUS);
    }

    private void updateProjectApply(ProjectApplyUpdateParam param, boolean isSubmit) {
        String userId = SecurityUtils.getUser().getUserId();
        ProjectApply projectApply = MapperUtil.nf().map(param, ProjectApply.class);
        projectApply.setUserId(userId);
        // 更新表单
        projectApplyService.updateProjectApply(projectApply, isSubmit);
        // 删除附件
        this.removeAttachmentFile(userId, param.getRemovedAttachmentIds());
        // 检查附件大小
        AssertUtil.assertThrow("附件大小不可超过50M，单类附件不可超过5个", !this.checkAttachmentSize(projectApply.getPid(), param));
        // 更新附件
        this.saveAttachmentFiles(userId, projectApply.getPid(), param.getResearchPlanAttachments(), AttachmentType.RESEARCH_PLAN);
        this.saveAttachmentFiles(userId, projectApply.getPid(), param.getResumeAttachments(), AttachmentType.RESUME);
        this.saveAttachmentFiles(userId, projectApply.getPid(), param.getMagnumOpusAttachments(), AttachmentType.MAGNUM_OPUS);
    }


    private void updateProjectAcceptanceApply(ProjectAcceptanceUpdateParam param, boolean isSubmit) {
        String userId = SecurityUtils.getUserId();

        ProjectApplyBO projectApply = projectApplyService.getProjectApply(param.getFlowInstanceId(), param.getPid());
        param.setPid(projectApply.getPid());
        // 检查附件
        this.checkAcceptanceAttachmentsSize(userId, param);

        // 更新数据
        projectApplyService.updateProjectAcceptance(param.getPid(), userId, param.getAcceptanceInstruction(), isSubmit);
        this.saveAttachmentFiles(userId, param.getPid(), param.getInstructionAttachments(), AttachmentType.ACCEPTANCE_INSTRUCTION);
        this.saveAttachmentFiles(userId, param.getPid(), param.getReportAttachments(), AttachmentType.ACCEPTANCE_REPORT);
        this.saveAttachmentFiles(userId, param.getPid(), param.getPaperAttachments(), AttachmentType.RESEARCH_PAPER);
        this.saveAttachmentFiles(userId, param.getPid(), param.getContractAttachments(), AttachmentType.PROJECT_CONTRACT);
        this.saveAttachmentFiles(userId, param.getPid(), param.getCalcDataAttachments(), AttachmentType.CALCULATE_DATA);
    }

    private void saveAttachmentFiles(String userId, String projectApplyId, MultipartFile[] files, AttachmentType attachmentType) {
        if (Objects.isNull(files))
            return;
        for (MultipartFile file : files) {
            // 保存附件
            ResourceFileStorageBO fileBO = new ResourceFileStorageBO();
            fileBO.setCatalog(storageConfig.getPersonalSpaceCatalog());
            fileBO.setSupId(StorageUtil.getProperFolderId(userId, StorageSpaceEnum.ATTACHMENT_SPACE.getCode()));
            fileBO.setName(file.getOriginalFilename());
            fileBO.setLength(file.getSize());
            ResourceFileStorageBO savedFileBO = resourceFileService.saveFile(fileBO, file, ConflictModeEnum.ALLOW);

            // 创建关联关系
            ProjectAttachment projectAttachment = new ProjectAttachment();
            projectAttachment.setProjectApplyId(projectApplyId);
            projectAttachment.setResourceId(savedFileBO.getPid());
            projectAttachment.setAttachmentType(attachmentType.code());
            projectAttachmentService.save(projectAttachment);
        }
    }

    private void removeAttachmentFile(String userId, List<String> resourceIds) {
        if (CollUtil.isEmpty(resourceIds)) {
            return;
        }
        resourceFileService.assertAuthOwn(userId, resourceIds);
        resourceFileService.deleteFiles(resourceIds);
        projectAttachmentService.deleteAttachments(resourceIds);
    }

    private void checkAcceptanceAttachmentsSize(String userId, ProjectAcceptanceUpdateParam param) {
        // 文件总个数上限10，总大小上限50M
        if (param.getRemovedAttachmentIds() != null) {
            this.removeAttachmentFile(userId, param.getRemovedAttachmentIds());
        }
        int totalCount = 0;
        long totalSize = 0L;
        // 检查已经上传的附件
        List<String> savedAttachments = projectAttachmentService.listProjectAttachmentIds(param.getPid(),
            AttachmentType.ACCEPTANCE_INSTRUCTION.code(), AttachmentType.ACCEPTANCE_REPORT.code(),
            AttachmentType.RESEARCH_PAPER.code(), AttachmentType.PROJECT_CONTRACT.code(),AttachmentType.CALCULATE_DATA.code());
        long savedAttachmentsSize = resourceFileService.countSizeBySupId(savedAttachments, null, null);
        totalCount += savedAttachments.size();
        totalSize += savedAttachmentsSize;

        // 检查刚上传的附件
        if (param.getInstructionAttachments() != null) {
            totalCount += param.getInstructionAttachments().length;
            totalSize += Arrays.stream(param.getInstructionAttachments()).mapToLong(MultipartFile::getSize).sum();
        }
        if (param.getReportAttachments() != null) {
            totalCount += param.getReportAttachments().length;
            totalSize += Arrays.stream(param.getReportAttachments()).mapToLong(MultipartFile::getSize).sum();
        }
        if (param.getPaperAttachments() != null) {
            totalCount += param.getPaperAttachments().length;
            totalSize += Arrays.stream(param.getPaperAttachments()).mapToLong(MultipartFile::getSize).sum();
        }
        if (param.getContractAttachments() != null) {
            totalCount += param.getContractAttachments().length;
            totalSize += Arrays.stream(param.getContractAttachments()).mapToLong(MultipartFile::getSize).sum();
        }
        if (param.getCalcDataAttachments() != null) {
            totalCount += param.getCalcDataAttachments().length;
            totalSize += Arrays.stream(param.getCalcDataAttachments()).mapToLong(MultipartFile::getSize).sum();
        }

        Assert.isTrue(totalCount <= ProjectConsts.MAX_ACCEPTANCE_ATTACHMENT_COUNT,
            "文件总个数不可超过" + ProjectConsts.MAX_ACCEPTANCE_ATTACHMENT_COUNT);
        Assert.isTrue(totalSize <= ProjectConsts.MAX_ACCEPTANCE_ATTACHMENT_SIZE,
            "文件总大小不可超过50M");
    }


}
